﻿/*
 * Copyright 2014 Francois Karman
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using Common.Logging;
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Quartz.WebManager.Logic
{
    /// <summary>
    /// Configures app domains to load job assemblies.
    /// </summary>
    public static class AppDomainConfiguration
    {
        /// <summary>
        /// The logger associated with the class.
        /// </summary>
        private static readonly ILog logger = LogManager.GetLogger(typeof(AppDomainConfiguration));

        /// <summary>
        /// The object used to makethe methods of this class thread-safe.
        /// </summary>
        private static readonly object lockObject = new object();

        /// <summary>
        /// Configures a specific app domain and updates its <see cref="AppDomain.AssemblyResolve"/> event.
        /// </summary>
        /// <param name="appDomain">The app domain to update.</param>
        public static void Configure(AppDomain appDomain)
        {
            if (appDomain == null)
            {
                throw new ArgumentNullException("appDomain");
            }

            appDomain.AssemblyResolve += OnDomainAssemblyResolve;
        }

        /// <summary>
        /// Called when an assembly is requested by the system.
        /// </summary>
        /// <param name="sender">The app domain that tries to load an assembly.</param>
        /// <param name="args">The details about the assembly to load.</param>
        /// <returns>The loaded assembly; or <c>null</c>.</returns>
        private static Assembly OnDomainAssemblyResolve(object sender, ResolveEventArgs args)
        {
            AppDomain appDomain = sender as AppDomain;
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
            else if (args == null)
            {
                throw new ArgumentNullException("args");
            }
            else if (appDomain == null)
            {
                throw new ArgumentException("should be a AppDomain instance", "sender");
            }

            // Loads jobs assembly, if not already done
            lock (lockObject)
            {
                try
                {
                    appDomain.AssemblyResolve -= OnDomainAssemblyResolve;
                    JobDefinitionManager.Initialize();
                }
                finally
                {
                    appDomain.AssemblyResolve += OnDomainAssemblyResolve;
                }
            }

            // Initialize the result variable
            Assembly assembly;

            // Try to find in already loaded assemblies
            // Note: A choice is made to distinguish calls that use full name and calls that use partial name.
            if (args.Name.Contains(","))
            {
                assembly = appDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
            }
            else
            {
                assembly = appDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == args.Name);
            }

            if (assembly != null)
            {
                return assembly;
            }

            // Try to find in GAC
            // Note: As the event handler is removed, the operation should be thread-safe
            lock (lockObject)
            {
                try
                {
                    appDomain.AssemblyResolve -= OnDomainAssemblyResolve;
                    try
                    {
                        assembly = Assembly.Load(args.Name);
                    }
                    catch (FileNotFoundException)
                    {
                        // Do nothing
                    }
                }
                finally
                {
                    appDomain.AssemblyResolve += OnDomainAssemblyResolve;
                }
            }

            if (assembly != null)
            {
                return assembly;
            }

            // The file was not found
            logger.WarnFormat("The assembly {0} was requested and not loadable - one possible cause is a missing resource assembly", args.Name);
            return null;
        }
    }
}
